//  SMPSO.java
//
//  Author:
//       Antonio J. Nebro <antonio@lcc.uma.es>
//       Juan J. Durillo <durillo@lcc.uma.es>
//
//  Copyright (c) 2011 Antonio J. Nebro, Juan J. Durillo
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
// 
//  You should have received a copy of the GNU Lesser General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

package hidra.many.metaheuristics.approaches.CSMOPSOv4;

import hidra.core.population.HIDRAPopulationAlgorithm;
import hidra.experiments.GlobalSettings;
import hidra.jmetal.core.Operator;
import hidra.jmetal.core.Problem;
import hidra.jmetal.core.Solution;
import hidra.jmetal.core.SolutionSet;
import hidra.qualityIndicator.Hypervolume;
import hidra.qualityIndicator.QualityIndicator;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import jmetal.operators.crossover.Crossover;
import jmetal.operators.crossover.CrossoverFactory;
import jmetal.operators.mutation.Mutation;
import jmetal.operators.mutation.MutationFactory;
import jmetal.util.Distance;
import jmetal.util.JMException;
import jmetal.util.PseudoRandom;
import jmetal.util.comparators.CrowdingDistanceComparator;
import jmetal.util.comparators.DominanceComparator;
import jmetal.util.comparators.FitnessComparator;
import jmetal.util.comparators.ObjectiveComparator;
import jmetal.util.wrapper.XReal;

/**
 * This class implements the SMPSO algorithm described in:
 * A.J. Nebro, J.J. Durillo, J. Garcia-Nieto, C.A. Coello Coello, F. Luna and E. Alba
 * "SMPSO: A New PSO-based Metaheuristic for Multi-objective Optimization". 
 * IEEE Symposium on Computational Intelligence in Multicriteria Decision-Making 
 * (MCDM 2009), pp: 66-73. March 2009
 */
public class SUB_MOPSO {

	/**
	 * Stores the number of particles_ used
	 */
	private int swarmSize_;
	/**
	 * Stores the maximum size for the archive
	 */
	private int archiveSize_;
	
	private SolutionSet particles_;
	/**
	 * Stores the best_ solutions founds so far for each particles
	 */
	private Solution[] best_;
	/**
	 * Stores the leaders_
	 */
	private ModifiedCrowdingArchive leaders_;
	/**
	 * Stores the speed_ of each particle
	 */
	private double[][] speed_;
	/**
	 * Stores a comparator for checking dominance
	 */
	private Comparator dominance_;
	/**
	 * Stores a comparator for crowding checking
	 */
	private Comparator crowdingDistanceComparator_;
	/**
	 * Stores a <code>Distance</code> object
	 */
	private Distance distance_;
	/**
	 * Stores a operator for non uniform mutations
	 */
	private Operator polynomialMutation_;

	private Problem problem_Original;
	

	private double[] ideal;
	private int index = 0;
	
	private Problem problem_Modified;
	

	double r1Max_;
	double r1Min_;
	double r2Max_;
	double r2Min_;
	double C1Max_;
	double C1Min_;
	double C2Max_;
	double C2Min_;
	double WMax_;
	double WMin_;
	double ChVel1_;
	double ChVel2_;

	private double trueHypervolume_;
	private Hypervolume hy_;
	private SolutionSet trueFront_;
	private double deltaMax_[];
	private double deltaMin_[];


	protected int iteration_;

	protected QualityIndicator qualityIndicator;
	protected double globalTime_ ;	
	
	
	
	
	/** 
	 * Constructor
	 * @param problem Problem to solve
	 */
	public SUB_MOPSO(Problem problem, int swarmSize,int archiveSize, int index, int maxIterations) {

		this.problem_Original = problem;
		this.swarmSize_ = swarmSize;
		this.archiveSize_ = archiveSize;
		this.index = index;
		
		
		r1Max_ = 1.0;
		r1Min_ = 0.0;
		r2Max_ = 1.0;
		r2Min_ = 0.0;
		C1Max_ = 2.5;
		C1Min_ = 1.5;
		C2Max_ = 2.5;
		C2Min_ = 1.5;
		WMax_ = 0.1;
		WMin_ = 0.1;
		ChVel1_ = -1;
		ChVel2_ = -1;
	} // Constructor

	
	
	

	
	
	/**
	 * Initialize all parameter of the algorithm
	 * @throws JMException 
	 */
	public void initParams() throws JMException {

				
		HashMap parameters = new HashMap() ;
        parameters.put("probability", 1.0/problem_Original.getNumberOfVariables()) ;
        parameters.put("distributionIndex", 20.0) ;
        Mutation mutation = MutationFactory.getMutationOperator("PolynomialMutation", parameters);                    

		
		
		polynomialMutation_ = mutation;

		archiveSize_ = 2 * archiveSize_;

		iteration_ = 0 ;





		ideal = new double[problem_Original.getNumberOfObjectives()];

		problem_Modified = new NADIRProblem(problem_Original, ideal, index);

		particles_ = new SolutionSet(swarmSize_);
		best_ = new Solution[swarmSize_];
		leaders_ = new ModifiedCrowdingArchive(archiveSize_, problem_Modified.getNumberOfObjectives());

		// Create comparators for dominance and crowding distance
		dominance_ = new DominanceComparator();
		crowdingDistanceComparator_ = new CrowdingDistanceComparator();
		distance_ = new Distance();

		// Create the speed_ vector
		speed_ = new double[swarmSize_][problem_Modified.getNumberOfVariables()];


		deltaMax_ = new double[problem_Modified.getNumberOfVariables()];
		deltaMin_ = new double[problem_Modified.getNumberOfVariables()];
		for (int i = 0; i < problem_Modified.getNumberOfVariables(); i++) {
			deltaMax_[i] = (problem_Modified.getUpperLimit(i) -
					problem_Modified.getLowerLimit(i)) / 2.0;
			deltaMin_[i] = -deltaMax_[i];
		} // for
	} // initParams 

	// Adaptive inertia 
	private double inertiaWeight(double wma) {
		return wma; // - (((wma-wmin)*(double)iter)/(double)miter);
	} // inertiaWeight

	// constriction coefficient (M. Clerc)
	private double constrictionCoefficient(double c1, double c2) {
		double rho = c1 + c2;
		//rho = 1.0 ;
		if (rho <= 4) {
			return 1.0;
		} else {
			return 2 / (2 - rho - Math.sqrt(Math.pow(rho, 2.0) - 4.0 * rho));
		}
	} // constrictionCoefficient


	// velocity bounds
	private double velocityConstriction(double v, double[] deltaMax,
			double[] deltaMin, int variableIndex,
			int particleIndex) throws IOException {


		double result;

		double dmax = deltaMax[variableIndex];
		double dmin = deltaMin[variableIndex];

		result = v;

		if (v > dmax) {
			result = dmax;
		}

		if (v < dmin) {
			result = dmin;
		}

		return result;
	} // velocityConstriction

	/**
	 * Update the speed of each particle
	 * @throws JMException 
	 */
	private void computeSpeed() throws JMException, IOException {
		double r1, r2, W, C1, C2;
		double wmax, wmin, deltaMax, deltaMin;
		XReal bestGlobal;

		for (int i = 0; i < swarmSize_; i++) {
			XReal particle = new XReal(particles_.get(i)) ;
			XReal bestParticle = new XReal(best_[i]) ;

			//Select a global best_ for calculate the speed of particle i, bestGlobal
			Solution one, two;
			int pos1 = PseudoRandom.randInt(0, leaders_.size() - 1);
			int pos2 = PseudoRandom.randInt(0, leaders_.size() - 1);
			one = leaders_.get(pos1);
			two = leaders_.get(pos2);

			if (crowdingDistanceComparator_.compare(one, two) < 1) {
				bestGlobal = new XReal(one);
			} else {
				bestGlobal = new XReal(two);
				//Params for velocity equation
			}
			r1 = PseudoRandom.randDouble(r1Min_, r1Max_);
			r2 = PseudoRandom.randDouble(r2Min_, r2Max_);
			C1 = PseudoRandom.randDouble(C1Min_, C1Max_);
			C2 = PseudoRandom.randDouble(C2Min_, C2Max_);
			W = PseudoRandom.randDouble(WMin_, WMax_);
			//
			wmax = WMax_;
			wmin = WMin_;

			for (int var = 0; var < particle.getNumberOfDecisionVariables(); var++) {
				//Computing the velocity of this particle 
				speed_[i][var] = velocityConstriction(constrictionCoefficient(C1, C2) *
						(inertiaWeight(wmax) *
								speed_[i][var] +
								C1 * r1 * (bestParticle.getValue(var) -
										particle.getValue(var)) +
										C2 * r2 * (bestGlobal.getValue(var) -
												particle.getValue(var))), deltaMax_, //[var],
												deltaMin_, //[var], 
												var,
												i);
			}
		}
	} // computeSpeed

	/**
	 * Update the position of each particle
	 * @throws JMException 
	 */
	private void computeNewPositions() throws JMException {
		for (int i = 0; i < swarmSize_; i++) {
			XReal particle = new XReal(particles_.get(i)) ;
			for (int var = 0; var < particle.getNumberOfDecisionVariables(); var++) {
				particle.setValue(var, particle.getValue(var) +  speed_[i][var]) ;

				if (particle.getValue(var) < problem_Modified.getLowerLimit(var)) {
					particle.setValue(var, problem_Modified.getLowerLimit(var));
					speed_[i][var] = speed_[i][var] * ChVel1_; //    
				}
				if (particle.getValue(var) > problem_Modified.getUpperLimit(var)) {
					particle.setValue(var, problem_Modified.getUpperLimit(var));
					speed_[i][var] = speed_[i][var] * ChVel2_; //   
				}
			}
		}
	} // computeNewPositions

	/**
	 * Apply a mutation operator to some particles in the swarm
	 * @throws JMException 
	 */
	private void mopsoMutation() throws JMException {
		for (int i = 0; i < particles_.size(); i++) {
			if ( (i % 6) == 0)
				polynomialMutation_.execute(particles_.get(i)) ;
			//if (i % 3 == 0) { //particles_ mutated with a non-uniform mutation %3
			//  nonUniformMutation_.execute(particles_.get(i));
			//} else if (i % 3 == 1) { //particles_ mutated with a uniform mutation operator
			//  uniformMutation_.execute(particles_.get(i));
			//} else //particles_ without mutation
			//;
		}
	} // mopsoMutation

	


	protected SolutionSet getParetoFront() {
		// TODO Auto-generated method stub
		return this.leaders_;
	}






	
	protected SolutionSet initializationAlgorithm() throws ClassNotFoundException,
	JMException {

		initParams();

		for (int i = 0; i < swarmSize_; i++) {
			Solution particle = new Solution(problem_Modified);	      
			particles_.add(particle);
		}

		double valuesMinObj[]   = ((NADIRProblem)problem_Modified).getMinimumValues(particles_);

		for(int i=0; i < valuesMinObj.length;  i++){
			ideal[i] = valuesMinObj[i];
		}

		for (int i = 0; i < swarmSize_; i++) {
			Solution particle = particles_.get(i);
			problem_Modified.evaluate(particle);
			problem_Modified.evaluateConstraints(particle);

		}

		//-> Step2. Initialize the speed_ of each particle to 0
		for (int i = 0; i < swarmSize_; i++) {
			for (int j = 0; j < problem_Modified.getNumberOfVariables(); j++) {
				speed_[i][j] = 0.0;
			}
		}

		// Step4 and 5   
		for (int i = 0; i < particles_.size(); i++) {
			Solution particle = new Solution(particles_.get(i));
			leaders_.add(particle);
		}

		//-> Step 6. Initialize the memory of each particle
		for (int i = 0; i < particles_.size(); i++) {
			Solution particle = new Solution(particles_.get(i));
			best_[i] = particle;
		}

		//Crowding the leaders_
		distance_.crowdingDistanceAssignment(leaders_, problem_Modified.getNumberOfObjectives());

		return this.leaders_;


	}







	private void localSearch() throws JMException{

		this.leaders_.sort(new ObjectiveComparator(0));

		int fitness1 = 0;
		int fitness2 = 0;

		for(int i=0; i < this.leaders_.size()  ;i++){	

			int f1 = i+1;
			int f2 = this.leaders_.size()-i+1;
			fitness1 = Math.max(f1, f2);
			this.leaders_.get(i).setFitness(fitness1);		  
		}



		this.leaders_.sort(new ObjectiveComparator(1));

		for(int i=0; i < this.leaders_.size()  ;i++){	
			int f1 = i+1;
			int f2 = this.leaders_.size()-i+1;
			fitness2 = Math.max(f1, f2);		  

			int fitness = Math.max(fitness2, (int)this.leaders_.get(i).getFitness());

			this.leaders_.get(i).setFitness(fitness);		  
		}





		this.leaders_.sort(new FitnessComparator());

		Solution solution = new Solution(leaders_.get(0));

		this.polynomialMutation_.execute(solution);

		this.problem_Modified.evaluate(solution);

		this.leaders_.add(solution);

	}







	private void crossOver() throws JMException{


		this.leaders_.sort(new ObjectiveComparator(0));

		Solution parent1 = this.leaders_.get(0);

		this.leaders_.sort(new ObjectiveComparator(1));


		Solution parent2 = this.leaders_.get(0);


		double crossoverProbability_        = GlobalSettings.crossoverProbability_  ;
		double mutationDistributionIndex_   = GlobalSettings.mutationDistributionIndex_ ;
		double crossoverDistributionIndex_  = GlobalSettings.crossoverDistributionIndex_ ;

		HashMap parameters = new HashMap() ;
		parameters.put("probability", crossoverProbability_) ;
		parameters.put("distributionIndex", crossoverDistributionIndex_) ;


		Crossover  crossover = 
			CrossoverFactory.getCrossoverOperator("SBXCrossover", parameters);

		Solution[] parents = new Solution[2];

		parents[0] = parent1;
		parents[1] = parent2;


		Solution[] offSpring = (Solution[])crossover.execute(parents);


		problem_Modified.evaluate(offSpring[0]);
		problem_Modified.evaluateConstraints(offSpring[0]);
		problem_Modified.evaluate(offSpring[1]);
		problem_Modified.evaluateConstraints(offSpring[1]);

		this.leaders_.add(offSpring[0]);
		this.leaders_.add(offSpring[1]);

	}


	
	protected SolutionSet runIteration() throws JMException {


		try {
			//Compute the speed_
			computeSpeed();
		} catch (IOException ex) {
			Logger.getLogger(SUB_MOPSO.class.getName()).log(Level.SEVERE, null, ex);
		}

		//Compute the new positions for the particles_            
		computeNewPositions();

		//Mutate the particles_          
		mopsoMutation();

		double temp[] 
		            = ((NADIRProblem)problem_Modified).getMinimumValues(particles_);

		for(int i=0; i < temp.length;  i++){
			ideal[i] = Math.min(ideal[i], temp[i]);
		}

		//Evaluate the new particles_ in new positions
		for (int i = 0; i < particles_.size(); i++) {
			Solution particle = particles_.get(i);
			problem_Modified.evaluate(particle);
		}

		
		//Actualize the archive          
		for (int i = 0; i < particles_.size(); i++) {
			Solution particle = new Solution(particles_.get(i));
			leaders_.add(particle);
		}

		
		crossOver();
		localSearch();

		
		leaders_.sort(Collections.reverseOrder(new ObjectiveComparator(0)));

		if(this.leaders_.size() > (archiveSize_/2) ){	      
			while(this.leaders_.size() > (archiveSize_/2)){
				this.leaders_.remove(0);
			}
		}

		//Actualize the memory of this particle
		for (int i = 0; i < particles_.size(); i++) {
			int flag = dominance_.compare(particles_.get(i), best_[i]);
			if (flag != 1) { // the new particle is best_ than the older remeber        
				Solution particle = new Solution(particles_.get(i));
				best_[i] = particle;
			}
		}

		//Assign crowding distance to the leaders_
		distance_.crowdingDistanceAssignment(leaders_,
				problem_Modified.getNumberOfObjectives());
		iteration_++;


		return this.leaders_;

	}


	public void insertInExternArchive(SolutionSet leaders) throws JMException{


		for (int i = 0; i < leaders.size(); i++) {
			Solution solution = new Solution(leaders.get(i));
			problem_Modified.evaluate(solution);
			leaders_.add(solution);
		}


		leaders_.sort(Collections.reverseOrder(new ObjectiveComparator(0)));

		if(this.leaders_.size() > (archiveSize_/2) ){	      
			while(this.leaders_.size() > (archiveSize_/2)){
				this.leaders_.remove(0);
			}
		}


	}



	/** 
	 * Gets the leaders of the SMPSO algorithm
	 */
	public SolutionSet getLeader() {

		SolutionSet solutionSet = new SolutionSet(leaders_.size());  

		for(int i=0; i<leaders_.size();i++){
			solutionSet.add(leaders_.get(i));
		}

		return solutionSet;

	}  // getLeader   




} // SMPSO
